use async_trait::async_trait;
use r2d2_redis::redis::Commands;
use sea_orm::Set;

use crate::entities::za_user::ActiveModel;
use crate::payload::user::{UserCreatePayload, UserInfoPayload};
use crate::system::encrypt::Encrypt;
use crate::system::random::Random;
use crate::{
    entities::za_user::Model,
    facade::{builder::Builder, user::UserFacade},
    model::user::UserModel,
    system::{
        app::{DB_POOL_MAP, REDIS_POOL_MAP},
        error::AppError,
        result::Result,
    },
    utils::cache::Keys,
};
pub struct UserProvider {}

impl Builder for UserProvider {
    fn builder() -> Self {
        Self {}
    }
}
#[async_trait]
impl UserFacade for UserProvider {
    fn get_by_token(&self, token: String) -> Result<Option<UserInfoPayload>, AppError> {
        let redis_pool_hash = REDIS_POOL_MAP.clone();
        let redis_main_pool = redis_pool_hash.get("main").unwrap();
        let pool = redis_main_pool.get();
        // get redis pool error
        if pool.is_err() {
            return Err(AppError::new_app_redis(pool.err().unwrap()));
        }
        // get cache
        let mut redis_main_conn = pool.unwrap();
        let key = Keys::get_auth_token(token);
        let auth_cache = redis_main_conn.get(key);
        if auth_cache.is_err() {
            return Err(AppError::new_app_redis_cmd(auth_cache.err().unwrap()));
        }
        let auth_cache_val: Option<String> = auth_cache.unwrap();
        // cache value empty
        if auth_cache_val.is_none() {
            return Ok(None);
        }
        // cache auth parse
        let auth_user: Result<UserInfoPayload, serde_json::Error> =
            serde_json::from_str(&auth_cache_val.unwrap());
        if auth_user.is_err() {
            return Err(AppError::new_serde_json(auth_user.err().unwrap()));
        }
        Ok(Some(auth_user.unwrap()))
    }

    async fn get_by_user_id(&self, user_id: String) -> Result<Option<Model>, AppError> {
        let db_conn_hash = DB_POOL_MAP.clone();
        let db_main_conn = db_conn_hash.get("main").unwrap();

        let user_model = UserModel::new(db_main_conn);
        let user_info_ret = user_model.get_by_user_id(user_id).await;
        if user_info_ret.is_err() {
            return Err(AppError::new_db_json(user_info_ret.err().unwrap()));
        }
        return Ok(user_info_ret.unwrap());
    }

    async fn delete_by_user_id(&self, user_id: String) -> Result<Option<Model>, AppError> {
        let db_conn_hash = DB_POOL_MAP.clone();
        let db_main_conn = db_conn_hash.get("main").unwrap();

        let user_model = UserModel::new(db_main_conn);
        let user_info_ret = user_model.get_by_user_id(user_id).await;
        if user_info_ret.is_err() {
            return Err(AppError::new_db_json(user_info_ret.err().unwrap()));
        }
        return Ok(user_info_ret.unwrap());
    }

    async fn create(&self, payload: UserCreatePayload) -> Result<Model, AppError> {
        let db_conn_hash = DB_POOL_MAP.clone();
        let db_main_conn = db_conn_hash.get("main").unwrap();
        let password_salt_ret = Random::str(4);
        if password_salt_ret.is_err() {
            return Err(AppError::new_utf8_json(password_salt_ret.err().unwrap()));
        }
        let password_salt = password_salt_ret.unwrap();

        let active_model = ActiveModel {
            user_id: Set(Random::uuid_v4()),
            username: Set(payload.username),
            password_encrypt: Set(Encrypt::password_encrypt(
                payload.password,
                password_salt.clone(),
            )),
            password_salt: Set(password_salt),
            user_intro: Set(payload.user_intro),
            ..Default::default()
        };
        let user_model = UserModel::new(db_main_conn);
        let create_ret = user_model.create(active_model).await;
        if create_ret.is_err() {
            return Err(AppError::new_db_json(create_ret.err().unwrap()));
        }
        Ok(create_ret.unwrap())
    }
    async fn fetch(&self, page: u32, pagesize: u32) -> Result<Vec<Model>, AppError> {
        let db_conn_hash = DB_POOL_MAP.clone();
        let db_main_conn = db_conn_hash.get("main").unwrap();

        let user_model = UserModel::new(db_main_conn);
        let offset = (page - 1) * pagesize;
        let limit = pagesize;
        let user_list_ret = user_model.fetch(offset, limit).await;
        if user_list_ret.is_err() {
            return Err(AppError::new_db_json(user_list_ret.err().unwrap()));
        }
        Ok(user_list_ret.unwrap())
    }
}
